package edu.buffalo.cse.cse486_586.service;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.TreeMap;
import edu.buffalo.cse.cse486_586.entity.DeliveredMessage;
import edu.buffalo.cse.cse486_586.entity.Message;
import edu.buffalo.cse.cse486_586.entity.PendingMessage;
import edu.buffalo.cse.cse486_586.entity.VoteMessage;
import edu.buffalo.cse.cse486_586.util.AppUtil;
import edu.buffalo.cse.cse486_586.util.CommonUtil;

public class MessageBean {
	// message type
	public final static int PENDING_MESSAGE = 1;
	public final static int VOTE_MESSAGE = 2;
	public final static int FOREWARD_MESSAGE = 3;
	public final static int FINAL_MESSAGE = 4;

	private int nextMsgId;
	private int nextPurposedId;
	private Map<Integer, Integer> localVector;
	private int localPort;

	// received messages container
	Map<String, List<VoteMessage>> recievedVoteMsg;
	List<PendingMessage> receivedMsg;
	List<PendingMessage> deliverableMsg;
	// singleton
	private static MessageBean instance = null;

	protected MessageBean() {
		this.nextMsgId = 1;
		this.nextPurposedId = 1;
		this.receivedMsg = new LinkedList<PendingMessage>();
		this.recievedVoteMsg = new HashMap<String, List<VoteMessage>>();
		this.deliverableMsg = new LinkedList<PendingMessage>();
		this.localVector = new TreeMap<Integer, Integer>();
		// initiate local vector
		for (int id : AppUtil.PROCESS_ID) {
			this.localVector.put(id, 0);
		}
	}

	public static MessageBean getInstance() {
		if (instance == null) {
			instance = new MessageBean();
		}
		return instance;
	}

	public Message generateMessage(int messagetType, String data) {
		switch (messagetType) {
		case PENDING_MESSAGE:
			String messageId = this.localPort + "_" + nextMsgId;
			PendingMessage pm = new PendingMessage(messageId, data,
					this.localPort);
			pm.setForeward(false);
			nextMsgId++;
			int localVar = this.localVector.get(localPort);
			localVector.put(localPort, (localVar + 1));
			return pm;
		case VOTE_MESSAGE:
			// data = votedMsgId
			VoteMessage vm = new VoteMessage(data,
					String.valueOf(this.nextPurposedId), localPort);
			this.nextPurposedId++;
			return vm;
		case FOREWARD_MESSAGE:
			String messageId1 = this.localPort + "_" + nextMsgId;
			PendingMessage pm1 = new PendingMessage(messageId1, data,
					this.localPort);
			pm1.setForeward(true);
			nextMsgId++;
			int localVar1 = this.localVector.get(localPort);
			localVector.put(localPort, (localVar1 + 1));
			return pm1;
		case FINAL_MESSAGE:
			String toekn[] = data.split(",");
			VoteMessage vm1 = new VoteMessage(toekn[0], toekn[1], localPort);
			vm1.setFinal(true);
			return vm1;
		default:
			return null;
		}

	}

	public boolean isCausaulOrdering(PendingMessage pm) {
		boolean result = true;

		// check whether the sender process have received all messages that send
		// from local process
		// Log.d("MesssageBean", pm.getVector().toString() + "//"
		// + this.localVector.toString());
		if (Integer.valueOf(pm.getSenderPort()) == this.localPort)
			return true;

		if (pm.getVector().get(pm.getSenderPort()) != (this.localVector.get(pm
				.getSenderPort()) + 1)) {
			// Log.d("MessageBean", "11111111111111111");
			result = false;
		} else {
			for (Entry<Integer, Integer> item : localVector.entrySet()) {
				if (item.getKey() != pm.getSenderPort()
						&& item.getValue() < pm.getVector().get(item.getKey())) {
					// Log.d("MessageBean", "2222222222222");
					result = false;
					break;
				}
			}
		}

		return result;
	}

	/*
	 * get list of Pending message that meet casual ordering
	 */
	public List<PendingMessage> popByCausalOrder() {
		int curPoint = 0;
		List<PendingMessage> causalOrderedMsgs = new ArrayList<PendingMessage>();
		while (curPoint < this.receivedMsg.size()) {
			PendingMessage pm = this.receivedMsg.get(curPoint);
			if (isCausaulOrdering(pm)) {
				causalOrderedMsgs.add(pm);
			}
			curPoint++;
		}

		return causalOrderedMsgs;
	}

	

	public VoteMessage checkRcvVotedMessage(VoteMessage vm) {
		String votedMsgId = vm.getVotedMessageId();
		if (this.recievedVoteMsg.get(votedMsgId).size() == AppUtil.numDev) {
			List<VoteMessage> vmList = this.recievedVoteMsg.get(votedMsgId);
			VoteMessage result = Collections.max(vmList,
					new VoteMessageComparator());
			this.recievedVoteMsg.remove(votedMsgId);
			// set total order
			Iterator<PendingMessage> it = this.deliverableMsg.iterator();
			while (it.hasNext()) {
				PendingMessage pm = it.next();
				if (pm.getMessageId().equals(votedMsgId)) {
					pm.setStatus(AppUtil.DELIVERABLE);
					pm.setComparable(true);
					pm.setFinalVm(vm);
					// Log.d("MessageBean", "set total sequence");
				}
			}
			return result;
		}

		return null;
	}

	public void updateSquenceForPm(VoteMessage vm) {
		String votedMsgId = vm.getVotedMessageId();

		Iterator<PendingMessage> it = this.deliverableMsg.iterator();
		while (it.hasNext()) {
			PendingMessage pm = it.next();
			if (pm.getMessageId().equals(votedMsgId)) {
				pm.setStatus(AppUtil.DELIVERABLE);
				pm.setComparable(true);
				pm.setFinalVm(vm);
				// Log.d("MessageBean", "updateSquenceForPm");
			}
		}
	}

	public List<DeliveredMessage> getPrintableMsg() {

		List<DeliveredMessage> dmList = new ArrayList<DeliveredMessage>();
		Collections.sort(this.deliverableMsg, new DeliveredMessageComparator());
		Iterator<PendingMessage> it = this.deliverableMsg.iterator();
		for (; it.hasNext();) {
			PendingMessage pm = it.next();
			DeliveredMessage dm = new DeliveredMessage(0, pm.getMessageId(),
					pm.getSenderPort() + ":" + pm.getContent());
			dmList.add(dm);
			it.remove();
		}

		return dmList;
	}

	public void addVotedMsg(String votedMsgId, VoteMessage vm) {
		if (this.recievedVoteMsg.containsKey(votedMsgId)) {
			this.recievedVoteMsg.get(votedMsgId).add(vm);
		} else {
			List<VoteMessage> list = new ArrayList<VoteMessage>();
			list.add(vm);
			this.recievedVoteMsg.put(votedMsgId, list);
		}
	}

	public void addPendingMsg(PendingMessage pm) {
		this.receivedMsg.add(pm);
		// Log.d("MessageBean",
		// "size of queing message:" + this.receivedMsg.size());
	}

	public void addToCausalOrderingQueue(PendingMessage pm) {
		this.deliverableMsg.add(pm);
	}

	public void updateLocalVector(PendingMessage pm) {
		// we don't want to update the local vector if the message is from
		// itself
		if (Integer.valueOf(pm.getSenderPort()) != this.localPort) {
			int oldVal = this.localVector.get(pm.getSenderPort());
			this.localVector.put(pm.getSenderPort(), oldVal + 1);
		}
	}

	public boolean isForward(PendingMessage pm) {
		String srcPort[] = pm.getContent().split(":");
		if (srcPort.length == 2 && CommonUtil.isNumeric(srcPort[0])) {
			if (Integer.parseInt(srcPort[0]) != this.localPort) {
				return false;
			}else{
				return true;
			}
		}else{
			return false;
		}
	}

	public String getLocalVectorString() {
		String str = "";
		for (Map.Entry<Integer, Integer> item : this.localVector.entrySet()) {
			str += item.getValue();
			str += ",";
		}

		str.substring(0, str.length() - 1);
		return str;
	}

	public int getRedirectPort(int consolePort) {
		int index = 0;
		for (int i = 0; i < AppUtil.PROCESS_ID.length; i++) {
			if (AppUtil.PROCESS_ID[i] == consolePort) {
				index = i;
				break;
			}
		}

		return AppUtil.redirect_port[index];
	}

	public int getNextMsgId() {
		return nextMsgId;
	}

	public void setNextMsgId(int nextMsgId) {
		this.nextMsgId = nextMsgId;
	}

	public int getNextPurposedId() {
		return nextPurposedId;
	}

	public void setNextPurposedId(int nextPurposedId) {
		this.nextPurposedId = nextPurposedId;
	}

	public Map<Integer, Integer> getLocalVector() {
		return localVector;
	}

	public void setLocalVector(Map<Integer, Integer> msgVector) {
		this.localVector = msgVector;
	}

	public int getLocalPort() {
		return localPort;
	}

	public void setLocalPort(int localPort) {
		this.localPort = localPort;
	}

	public Map<String, List<VoteMessage>> getRecievedVoteMsg() {
		return recievedVoteMsg;
	}

	public void setRecievedVoteMsg(
			Map<String, List<VoteMessage>> recievedVoteMsg) {
		this.recievedVoteMsg = recievedVoteMsg;
	}

	public List<PendingMessage> getRecievedMsgs() {
		return receivedMsg;
	}
}
